#!/usr/bin/env python
import os
import sys
import logging

import cairo

from canvaside.style import parseColor, formatColor
from canvaside.utils import safe

from base import BaseCanvasRenderingContext2D

class ContextState(object):
    def __init__(self):
        self.colorStyles = { 'fill' : parseColor('black'), 'stroke' : parseColor('black') }
        self.globalAlpha = 1.0

    def clone(self):
        from copy import deepcopy

        return deepcopy(self)

    def getFillStyle(self):
        return formatColor(self.colorStyles['fill'])

    def setFillStyle(self, style):
        self.colorStyles['fill'] = parseColor(style)

    fillStyle = property(getFillStyle, setFillStyle)

    def getStrokeStyle(self):
        return formatColor(self.colorStyles['stroke'])

    def setStrokeStyle(self, style):
        self.colorStyles['stroke'] = parseColor(style)

    strokeStyle = property(getStrokeStyle, setStrokeStyle)

class CanvasRenderingContext2D(BaseCanvasRenderingContext2D):
    logger = logging.getLogger('canvaside.canvas.cairo')

    def __init__(self, canvas):
        BaseCanvasRenderingContext2D.__init__(self, canvas)

        self._width = canvas.width
        self._height = canvas.height

        self.img = cairo.ImageSurface(cairo.FORMAT_ARGB32, canvas.width, canvas.height)
        self.ctxt = cairo.Context(self.img)

        self.states = [ContextState()]

    @property
    def width(self):
        return self._width

    @property
    def height(self):
        return self._height

    @property
    def state(self):
        return self.states[-1]

    @safe
    def save(self):
        """push state on state stack"""
        self.states.append(self.state.clone())
        self.ctxt.save()

    @safe
    def restore(self):
        """pop state stack and restore state"""
        self.states.pop()
        self.ctxt.restore()

    def getGlobalAlpha(self):
        return self.state.globalAlpha

    def setGlobalAlpha(self, value):
        if 0 <= value and value <= 1.0:
            self.state.globalAlpha = value

    globalAlpha = property(getGlobalAlpha, setGlobalAlpha,
                           doc="the current alpha value applied to rendering operations.")

    GRAPHICS_OPERATORS = {
        'clear': cairo.OPERATOR_CLEAR,      # clear destination layer (bounded)
        'copy': cairo.OPERATOR_SOURCE,      # replace destination layer (bounded)
        'over': cairo.OPERATOR_OVER,        # draw source layer on top of destination layer (bounded)
        'source-over': cairo.OPERATOR_OVER, # draw source layer on top of destination layer (bounded)
        'source-in': cairo.OPERATOR_IN,     # draw source where there was destination content (unbounded)
        'source-out': cairo.OPERATOR_OUT,   # draw source where there was no destination content (unbounded)
        'source-atop': cairo.OPERATOR_ATOP, # draw source on top of destination content and only there
        'dest': cairo.OPERATOR_DEST,        # ignore the source
        'destination-over': cairo.OPERATOR_DEST_OVER,   # draw destination on top of source
        'destination-in': cairo.OPERATOR_DEST_IN,       # leave destination only where there was source content (unbounded)
        'destination-out': cairo.OPERATOR_DEST_OUT,     # leave destination only where there was no source content
        'destination-atop': cairo.OPERATOR_DEST_ATOP,   # leave destination on top of source content and only there (unbounded)
        'xor': cairo.OPERATOR_XOR,          # source and destination are shown where there is only one of them
        'lighter': cairo.OPERATOR_ADD,      # source and destination layers are accumulated
        'darker': cairo.OPERATOR_SATURATE,  #like over, but assuming source and dest are disjoint geometries
    }

    def getGlobalCompositeOperation(self):
        op = self.ctxt.get_operator()

        for name, value in self.GRAPHICS_OPERATORS.items():
            if value == op:
                return name

        self.logger.error("get an unknonw composite operator: %s", op)

        raise IndexError()

    def setGlobalCompositeOperation(self, value):
        op = self.GRAPHICS_OPERATORS.get(value, None)

        if op:
            self.ctxt.set_operator(op)
        else:
            self.logger.warn("set an unknown composite operator: %s", value)

    globalCompositeOperation = property(getGlobalCompositeOperation, setGlobalCompositeOperation,
                                        doc="the current composition operation")

    def getFillStyle(self):
        return self.state.fillStyle

    def setFillStyle(self, style):
        self.state.fillStyle = style

    fillStyle = property(getFillStyle, setFillStyle,
                         doc="the current style used for filling shapes.")

    def getStrokeStyle(self):
        return self.state.strokeStyle

    def setStrokeStyle(self, style):
        self.state.strokeStyle = style

    strokeStyle = property(getStrokeStyle, setStrokeStyle,
                           doc="the current style used for stroking shapes.")

    @safe
    def show(self):
        import tempfile

        file = tempfile.mktemp(suffix='.png')

        self.img.write_to_png(file)

        if sys.platform == "win32":
            os.system("start /wait %s && del /f %s" % (file, file))
        else:
            logging.warn("show method does not implemented on %s platform", sys.platform)

    def contextState(self):
        ctxt = self.ctxt

        class ContextState(object):
            def __enter__(self):
                ctxt.save()

            def __exit__(self, exc_type, exc_value, traceback):
                ctxt.restore()

        return ContextState()

    def contextPath(self):
        ctxt = self.ctxt
        state = self.state

        class ContextPath(object):
            def __enter__(self):
                self.path = ctxt.copy_path()
                ctxt.new_path()

                return self

            def __exit__(self, exc_type, exc_value, traceback):
                ctxt.new_path()
                ctxt.append_path(self.path)

            def fill(self):
                self.draw(True)

            def stroke(self):
                self.draw(False)

            def draw(self, fill):
                r, g, b, a = state.colorStyles['fill' if fill else 'stroke']

                a *= state.globalAlpha

                ctxt.set_source_rgba(r, g, b, a/255.0)

                ctxt.fill() if fill else ctxt.stroke()

        return ContextPath()

    def drawRect(self, x, y, w, h, fill):
        with self.contextPath() as path:
            self.ctxt.rectangle(x, y, w, h)

            path.draw(fill)

    @safe
    def clearRect(self, x, y, w, h):
        """Clears all pixels on the canvas in the given rectangle to transparent black."""
        with self.contextPath() as path:
            with self.contextState():
                self.ctxt.set_operator(cairo.OPERATOR_CLEAR)
                self.ctxt.rectangle(x, y, w, h)

                path.fill()

    @safe
    def fillRect(self, x, y, w, h):
        """Paints the given rectangle onto the canvas, using the current fill style."""
        self.drawRect(x, y, w, h, True)

    @safe
    def strokeRect(self, x, y, w, h):
        """Paints the box that outlines the given rectangle onto the canvas, using the current stroke style."""
        self.drawRect(x, y, w, h, False)
